package asteroids.model.programs.expressions;

import asteroids.Util;
import asteroids.model.GameObject;
import asteroids.model.Ship;

public class ExprBoolEvaluator implements ExprEvaluator<Boolean> {

	public ExprBoolEvaluator(Ship ship) {
		this.ship = ship;
		ede = new ExprDoubleEvaluator(ship);
		eee = new ExprEntityEvaluator(ship);
	}
	
	//WATCH OUT: private final ExprDoubleEvaluator ede = new ExprDoubleEvaluator(getSelf()) getSelf == null!!!!
	private final ExprDoubleEvaluator ede;
	
	private ExprEntityEvaluator getEEE() {
		return eee;
	}
	
	private final ExprEntityEvaluator eee;
	
	public Ship getShip(){
		return ship;
	}

	private final Ship ship;
	

	@Override
	public Boolean evaluate(And and) {
		boolean b1 = and.getExpr1().accept(this);
		boolean b2 = and.getExpr2().accept(this);
		return b1 && b2;
	}

	@Override
	public Boolean evaluate(Or or) {
		boolean b1 = or.getExpr1().accept(this);
		boolean b2 = or.getExpr2().accept(this);
		return b1 || b2;
	}

	@Override
	public Boolean evaluate(LessThan lt) {
		double d1 = lt.getExpr1().accept(ede);
		double d2 = lt.getExpr2().accept(ede);
		return d1 < d2;
	}

	@Override
	public Boolean evaluate(GreaterThan gt) {
		double d1 = gt.getExpr1().accept(ede);
		double d2 = gt.getExpr2().accept(ede);
		return d1 > d2;
	}

	@Override
	public Boolean evaluate(Equality eq) {
		boolean result;
		try {
			double d1 = eq.getExpr1().accept(ede);
			double d2 = eq.getExpr2().accept(ede);
			result = Util.fuzzyEquals(d1, d2);
		}
		catch(IllegalArgumentException exc) {
			try {
				GameObject go1 = eq.getExpr1().accept(getEEE());
				GameObject go2 = eq.getExpr2().accept(getEEE());
				result = (go1 == go2);
			}
			catch(IllegalArgumentException exc2) {
				throw new IllegalArgumentException("== operator failed to evaluate  at line "+eq.getLine()+", column "+eq.getColumn()+"!", exc2);
			}
		}
		return result;
	}

	@Override
	public Boolean evaluate(Add add) {
		throw new IllegalArgumentException("+ operator cannot evaluate to a boolean at line "+add.getLine()+", column "+add.getColumn()+"!");
	}

	@Override
	public Boolean evaluate(Subtract sub) {
		throw new IllegalArgumentException("- operator cannot evaluate to a boolean at line "+sub.getLine()+", column "+sub.getColumn()+"!");
	}

	@Override
	public Boolean evaluate(Division div) {
		throw new IllegalArgumentException("/ operator cannot evaluate to a boolean at line "+div.getLine()+", column "+div.getColumn()+"!");
	}

	@Override
	public Boolean evaluate(Multiply mul) {
		throw new IllegalArgumentException("* operator cannot evaluate to a boolean at line "+mul.getLine()+", column "+mul.getColumn()+"!");
	}

	@Override
	public Boolean evaluate(DoubleLiteral d) {
		throw new IllegalArgumentException("DoubleLiteral cannot evaluate to a boolean at line "+d.getLine()+", column "+d.getColumn()+"!");
	}

	@Override
	public Boolean evaluate(BooleanLiteral b) {
		return b.getB();
	}

	@Override
	public Boolean evaluate(Not not) {
		boolean negate = not.getExpr().accept(this);
		return !negate;
	}

	@Override
	public Boolean evaluate(Sqrt sqrt) {
		throw new IllegalArgumentException("Math.sqrt() cannot evaluate to a boolean at line "+sqrt.getLine()+", column "+sqrt.getColumn()+"!");
	}

	@Override
	public Boolean evaluate(Sin sin) {
		throw new IllegalArgumentException("Math.sin() cannot evaluate to a boolean at line "+sin.getLine()+", column "+sin.getColumn()+"!");
	}

	@Override
	public Boolean evaluate(Cos cos) {
		throw new IllegalArgumentException("Math.cos() cannot evaluate to a boolean at line "+cos.getLine()+", column "+cos.getColumn()+"!");
	}

	@Override
	public Boolean evaluate(GetDirection getDir) {
		throw new IllegalArgumentException("GetDirection cannot evaluate to a boolean at line "+getDir.getLine()+", column "+getDir.getColumn()+"!");
	}

	@Override
	public Boolean evaluate(Self self) {
		throw new IllegalArgumentException("Self cannot evaluate to a boolean at line "+self.getLine()+", column "+self.getColumn()+"!");
	}

	@Override
	public Boolean evaluate(GetX getX) {
		throw new IllegalArgumentException("GetX cannot evaluate to a boolean at line "+getX.getLine()+", column "+getX.getColumn()+"!");
	}

	@Override
	public Boolean evaluate(GetY getY) {
		throw new IllegalArgumentException("GetY cannot evaluate to a boolean at line "+getY.getLine()+", column "+getY.getColumn()+"!");
	}

	@Override
	public Boolean evaluate(GetVX getVX) {
		throw new IllegalArgumentException("GetVX cannot evaluate to a boolean at line "+getVX.getLine()+", column "+getVX.getColumn()+"!");
	}

	@Override
	public Boolean evaluate(GetVY getVY) {
		throw new IllegalArgumentException("GetVY cannot evaluate to a boolean at line "+getVY.getLine()+", column "+getVY.getColumn()+"!");
	}

	@Override
	public Boolean evaluate(GetRadius getRadius) {
		throw new IllegalArgumentException("GetRadius cannot evaluate to a boolean at line "+getRadius.getLine()+", column "+getRadius.getColumn()+"!");
	}

	@Override
	public Boolean evaluate(Variable var) {
		boolean b;
		try {
			b = getShip().getProgram().getAssignment(var.getName()).accept(this);
		}
		catch(IllegalArgumentException exc) {
			throw new IllegalArgumentException("Variable cannot evaluate to a boolean at line "+var.getLine()+", column "+var.getColumn()+"!", exc);
		}
		catch(NullPointerException exc) {
			throw new IllegalArgumentException("Encountered a null reference while trying to evaluate the variable at line "+var.getLine()+", column "+var.getColumn()+"!", exc);
		}
		return b;
	}

	@Override
	public Boolean evaluate(EntityLiteral e) {
		throw new IllegalArgumentException("EntityLiteral cannot evaluate to a boolean at line "+e.getLine()+", column "+e.getColumn()+"!");
	}

	@Override
	public Boolean evaluate(Null null1) {
		throw new IllegalArgumentException("Null cannot evaluate to a boolean at line "+null1.getLine()+", column "+null1.getColumn()+"!");
	}

}
